﻿using System;
using System.Linq;
using System.Collections.Generic;

namespace HashFoo.Core
{
    /// <summary>
    /// Math helper functions
    /// </summary>
    public static class MathUtil
    {
        /// <summary>
        /// Bounds a value to a specific range.
        /// </summary>
        /// <param name="val"></param>
        /// <param name="minInclusive"></param>
        /// <param name="maxInclusive"></param>
        /// <returns></returns>
        public static double BoundToRange(double val, double minInclusive, double maxInclusive)
        {
            if (val < minInclusive) return minInclusive;
            if (val > maxInclusive) return maxInclusive;

            return val;
        }

        /// <summary>
        /// Bounds a value to a specific range.
        /// </summary>
        /// <param name="val"></param>
        /// <param name="minInclusive"></param>
        /// <param name="maxInclusive"></param>
        /// <returns></returns>
        public static int BoundToRange(int val, int minInclusive, int maxInclusive)
        {
            if (val < minInclusive) return minInclusive;
            if (val > maxInclusive) return maxInclusive;

            return val;
        }

        /// <summary>
        /// Computes the standard deviation from a series of values.
        /// </summary>
        /// <param name="values"></param>
        /// <returns></returns>
        public static double StandardDeviation(IEnumerable<double> values)
        {
            if (values == null) throw new ArgumentNullException();

            // this is probably not very performant -- but it works
            // DMF 20120515
            var valArray = values.ToArray();
            if (valArray.Length == 0) throw new ArgumentException("values cannot be an empty sequence.");

            var avg = valArray.Average();
            return Math.Sqrt(valArray.Average(v => Math.Pow(v - avg, 2)));
        }

        /// <summary>
        /// Computes the standard deviation from a series of values.
        /// </summary>
        /// <param name="values"></param>
        /// <returns></returns>
        public static double StandardDeviation(IEnumerable<int> values)
        {
            return StandardDeviation(values.Cast<double>());
        }

        /// <summary>
        /// Computes the standard deviation from a series of values.
        /// </summary>
        /// <param name="values"></param>
        /// <returns></returns>
        public static double StandardDeviation(IEnumerable<float> values)
        {
            return StandardDeviation(values.Cast<double>());
        }

        /// <summary>
        /// Takes a point one range and converts it to a scaled equivalent point in another range.
        /// </summary>
        /// <param name="fromPoint">The point to convert.</param>
        /// <param name="fromRange">The range the <c>fromPoint</c> belongs to.</param>
        /// <param name="toRange">The range to scale the <c>fromPoint</c> to.</param>
        /// <returns>The scaled point in the <c>toRange</c>.</returns>
        public static double ScaleRange(double fromPoint, RangeF fromRange, RangeF toRange)
        {
            if (fromPoint > fromRange.Max || fromPoint < fromRange.Min)
            {
                throw new ArgumentOutOfRangeException("fromPoint", "fromPoint needs to be in the fromRange");
            }

            // find the percent location of the fromPoint in the fromRange;
            var fromOffset = fromPoint - fromRange.Min;
            var pointLocation = fromOffset / (fromRange.Max - fromRange.Min);

            // find the topoint offset using the inverse process
            var toPointOffset = pointLocation * (toRange.Max - toRange.Min);

            // create the point from the toRangeMin
            return toRange.Min + toPointOffset;
        }

        /// <summary>
        /// Takes a point one range and converts it to a scaled equivalent point in another range.
        /// </summary>
        /// <param name="fromPoint">The point to convert.</param>
        /// <param name="fromRange">The range the <c>fromPoint</c> belongs to.</param>
        /// <param name="toRange">The range to scale the <c>fromPoint</c> to.</param>
        /// <returns>The scaled point in the <c>toRange</c>.</returns>
        public static double ScaleRange(int fromPoint, Range fromRange, Range toRange)
        {
            return ScaleRange(
                fromPoint,
                new RangeF { Max = fromRange.Max, Min = fromRange.Min },
                new RangeF { Max = toRange.Max, Min = toRange.Min });
        }

        /// <summary>
        /// Rounds a decimal type value to an int.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static int RoundToInt(double value)
        {
            return (int)Math.Round(value, 0);
        }

        /// <summary>
        /// Rounds a decimal type value to an int.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static int RoundToInt(decimal value)
        {
            return (int)Math.Round(value, 0);
        }

        /// <summary>
        /// Converts a decimal percentage (like 0.1) to an integer percentage (10).
        /// </summary>
        /// <param name="percent"></param>
        /// <returns></returns>
        public static int ConvertToIntPercent(double percent)
        {
            return (int)(Math.Round(percent, 2) * 100);
        }
    }
}